Skip to content

feat: migrate shield-swap-sdk to the new shield_swap.aleo stack - #62

Open
iamalwaysuncomfortable wants to merge 23 commits into
masterfrom
feat/shield-swap-update
Open

feat: migrate shield-swap-sdk to the new shield_swap.aleo stack#62
iamalwaysuncomfortable wants to merge 23 commits into
masterfrom
feat/shield-swap-update

Conversation

@iamalwaysuncomfortable

Copy link
Copy Markdown
Member

Migrates shield-swap-sdk from the retired shield_swap_v3.aleo to the freshly deployed shield_swap.aleo stack, per the migration guide (amm-v3#62 target). Hard cutover — no state carries over.

Contract cutover

  • Wire layer regenerated from deployed testnet bytecode: PROGRAM_ID = shield_swap.aleo; U256 {hi, lo} prices, MerkleProof struct, PositionNFT.withdrawal, reshaped PoolState/Slot/Tick/Position/SwapOutput, new compliance records; claim_multi_hop_output and set_token_decimals gone.
  • Q128.128 math: new _MAGIC_X128 tick table + MIN/MAX_SQRT_RATIO_X128 pinned from the contract and verified against the reference implementation (f(±400000) matches exactly); u256_to_int / int_to_u256_plaintext helpers.
  • Raw native amounts end to end: no dust rule, no 9-decimal normalization; SlotView.price re-derived; collect_all requests exactly the chain-reported owed amounts.
  • Freezelist proofs: mint / claim_swap_output / collect carry [MerkleProof; 2] arrays — empty-tree defaults while the lists are empty, wrapper_proofs= override for later.
  • Immutable withdrawal: mint(withdrawal=…) (defaults to recipient) is stored on the NFT; collect always pays it — the recipient kwarg is removed.
  • Blinding: CLAIM_OR_SWAP_DOMAIN unchanged in the new bytecode; only DEFAULT_PROGRAM flips (v3 vectors kept as algorithm tests, new-program vectors pinned).

Automatic router dispatch

Every verb detects each token's wrapped/plain shape (from_wrapper_token_id chain probe, cached; transport errors propagate rather than misclassifying) and dispatches per the guide's §6 matrix:

  • swap → shield_swap_router.swap_from_wrapped when the input is wrapped (funded with underlying records; deposit + burn happen in-transaction; amount_out_min > 0 enforced at prepare time)
  • claim → 4-way dispatch on the finalized SwapOutput shapes (covers the plain-start/wrapped-claim asymmetry)
  • mint/increase/collect → the 11 LP-router transitions, wrapper proofs interleaved per the deployed input orders
  • decrease/burn → always direct

Record selection understands credits.aleo (microcredits) and skips recipient-bound wrapper records. Mirrored in AsyncShieldSwap.

Supporting changes

  • aleo.codegen (main SDK): fixed-length Array ABI type support (fmt_array, toposort/ref-check recursion into array elements).
  • API layer → staging: DEFAULT_API_URL and regen default now https://amm-api-staging.dev.provable.com (SHIELD_SWAP_API_URL override); models regenerated (wrapper_programamm_token_program, new underlying_program/underlying_token_id drive record-program resolution); redeem no longer rotates the session credential.
  • Devnode harness: deploys the new 5-program stack (core + multisig + freezelist + 2 test tokens), initializes the freezelist, registers plain tokens via the two-arg allow_token with idempotent bootstrap.

Verification

  • shield-swap-sdk unit suite: 171 passed
  • main SDK suite: 905 passed (includes codegen Array coverage)
  • live read + drift tier (real testnet + staging API): 11 passed — pinned ABI matches deployed bytecode, X128 invariants on live slots, local pool/tick key derivation parity, registry↔chain wrapped-detection agreement
  • devnode lifecycle (hermetic, full stack): 11 passed — pools → mint → increase → decrease → both-direction swaps + claims → owner≠withdrawal collect payout → burn
  • live write tiers (plain + wrapped swap roundtrips) are wired and skip cleanly pending DPS e2e credentials

- /auth/verify: send challenge_id from the challenge payload
- sessions are httpOnly cookies + X-CSRF-Token (legacy body-JWT kept)
- is_authenticated covers both credential kinds
- salted retry on DPS consumer-username collisions
- lifecycle live test: invite codes are pasted, never generated; shed
  SHIELD_SWAP_PRIVATE_KEY so the fresh-profile premise holds
…nt under cookie auth

- _headers: prefer the live session (csrf + cookies) over Authorization;
  ss_ tokens don't cover the /access tier and the server reads the header first
- 401 while both credentials are loaded drops the expired session and
  retries as bearer (15-min sessions)
- _auth_done recognizes cookie sessions; CSRF never persisted as jwt;
  from_profile prefers the durable ss_ token over stale session creds
Slot-derived insert hints only validate for a pool's FIRST position —
finalize asserts the true linked-list predecessors, so every later mint
was rejected on populated pools. find_tick_predecessor walks the ticks
mapping from the MIN sentinel (fresh pools anchor at the sentinel;
initialized ticks return themselves since validation is skipped).
… 0.3.0

shield-swap-sdk now floors aleo-sdk at 0.3 (Array codegen runtime).
Docs updated: staging auth/session + referral-vs-access invites, live-
verified LP behaviors (tick-list hints, wrapped-side exact amounts,
slippage headroom), sdk-abi example re-pointed at the new stack.
* docs: voice.md docstrings for the undocumented public surface

Adds docstrings to the 206 public classes/methods in sdk/python/aleo and
shield-swap-sdk that had none, following .agents/voice.md: present-tense
verb lead, side effects named (network / fee / local-only), and each
argument, return, and raised error described by consequence.

Section syntax follows each file's local idiom rather than one global
rule — numpy `Parameters` inside facade/ (54 existing blocks, and
voice.md shipped in the same commit as those files), Google `Args:`
elsewhere. shield-swap keeps its terser prose voice and its
"see :meth:`ApiClient.X`" convention for async mirrors. Where a sync
counterpart was already documented the text is mirrored, with
async-specific facts corrected: wait_for_transaction_confirmation yields
to the event loop via asyncio.sleep, AsyncDexCall.simulate explains why
it is not awaited, and three cross-references now point at the async
classes instead of RecordScanner / AleoNetworkClient.

Three behaviours were checked against the code rather than assumed:
get_block_range's end-inclusivity varies by node build (the e2e test
says so) so the docstring warns against relying on the last element;
decrypt_enabled gates find_credits_record(s), not owned(), which
decrypts opportunistically; and codegen's main() exits via argparse on a
usage error rather than returning 1.

Also removes ApiClient.generate_access_codes and its async mirror.
Minting invite/access codes should not be part of the SDK surface.
redeem_access_code stays, so a code obtained out-of-band still works,
and human-pasted referral invites keep going through redeem_code. Note
this reduces SDK surface, not access: POST /access/generate is still
reachable directly, and the real gate is the server-side generate right.
test_minting_access_codes_is_not_exposed guards the removal.

shield-swap-sdk/AGENTS.md is generated from these docstrings, so both
copies are regenerated. Four TIER2 entries (api.get_pools,
api.get_tokens, derive_pool_key, derive_tick_key) rendered with blank
bodies before and now carry real text, which pushed the page past
test_gen_context.py's compactness budget — that threshold moves 22k to
24k, with the reason recorded alongside the prior 20k to 22k raise.

Verified: 890 passed (sdk, -m "not slow"), 174 passed (shield-swap),
pyright strict 0 errors on sdk, gen_context.py --check clean. The 15
pyright errors in shield-swap-sdk are pre-existing and unchanged
(confirmed by re-running against HEAD).

* fix(facade): exact credits/microcredits conversion (#66)

Both directions went through binary floating point and lost value.

`credits_to_microcredits` multiplied by 1_000_000 as a float and then
truncated toward zero with int(), so ordinary amounts silently underpaid:
1.005 credits became 1_004_999 microcredits, not 1_005_000. Sub-microcredit
input was dropped with no error at all (0.9999999 -> 999_999).

`microcredits_to_credits` returned a float. Microcredits are a u64, and past
2**53 a float cannot hold the integer — u64 max round-tripped off by one, and
2884 of the first 200_000 microcredit values failed
`micro -> credits -> micro` identity.

Both now compute in Decimal. Float input is routed through str(), which
recovers the shortest representation that round-trips — i.e. the literal the
caller wrote — which is what rescues 1.005; str and Decimal input are exact
already. Sub-microcredit precision now raises ValueError naming the value,
with allow_rounding=True to opt back into truncation, so lost value is an
error rather than a silent underpayment.

`from_microcredits` returns Decimal instead of float. It still compares equal
to the obvious float, so existing assertions hold unchanged, but mixing it
into float arithmetic now raises — convert with float() deliberately if you
want that, accepting the loss.

Scope is contained: these are user-facing convenience helpers only. No
internal fee or amount path consumes them (the SDK is integer microcredits
end to end), and shield-swap does not use them.

Verified: 896 passed (sdk, -m "not slow"; +6 new), pyright strict 0 errors,
shield-swap 174 passed and unaffected.

* docs: clarify OwnedFilter uuid default wording

* docs: drop the self-hosted-scanner recommendation from record docstrings

* docs: drop redundant 'Hits the network' notes and the block-range build caveat

* docs: make get_pools/get_tokens concrete, name methods instead of 'verbs'

* docs(voice): ban "reach for" and vague hedges

* docs: drop self-hosted-scanner advice everywhere, keep the view-key disclosure

* docs(voice): drop self-hosting from the privacy stance

* docs: drop self-hosted-scanner mentions from the READMEs

* docs(shield-swap): say "methods", not "verbs"

* docs: tighten get_pools, explain why token info can be None

* docs: simplify the token-info None explanation

* docs(voice): require plain verbs; reword get_tokens decimals note

* docs: reword get_swap lag note and get_public_balances

* docs: state get_ohlcv's real granularity values and unix-second bounds

* docs: explain what the Journal is on the class itself

* docs: state that a profile holds one Aleo address

* docs: add a Profile create/load usage example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant